﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using System.ServiceModel;
using System.ServiceModel.Description;
using System.ServiceModel.Channels;
using System.Runtime.Serialization;
using System.Configuration;
using System.Xml;

using SsrsRenderStudio.Shared;
using SsrsRenderStudio.Configuration;
using SsrsRenderStudio.KnownTypesAndModels;

namespace SsrsRenderStudio.Wcf
{
    public static class WcfConfiguration
    {
        private static string _localEndpointsFile = string.Empty;
        private static Dictionary<string, List<string>> _listOfBindings = new Dictionary<string, List<string>>();
        private static Dictionary<string, int> _currentBindingPosition = new Dictionary<string, int>();

        /// <summary>
        /// Reset configuration state.  Next call to fetch config will reload all configurations.
        /// </summary>
        public static void Reset()
        {
            _localEndpointsFile = string.Empty;
        }

        /// <summary>
        /// Fetch the remote endpoints pointer configuration to local disk.
        /// </summary>
        /// <returns>Absolute path to the fetched configuration.</returns>
        private static string FetchRemoteEndpointsPointer()
        {
            ////DummyLogger.Log("fetchRemoteEndpntPointer");
            ////DummyLogger.Log("fetchRemoteEndpntPointer: curr dir: " + SsrsRenderStudio.Configuration.ConfigurationManager.GetCurrentDirectory());
            string endpointsDirectory = SsrsRenderStudio.Configuration.ConfigurationManager.GetCurrentDirectory() + @"\endpoints\";

            // create folder to store fetched data
            if (!System.IO.Directory.Exists(endpointsDirectory))
            {
                try
                {
                    //DummyLogger.Log("fetchRemoteEndpntPointer:create folder");
                    System.IO.Directory.CreateDirectory(endpointsDirectory);
                }
                catch (Exception ex)
                {
                    //DummyLogger.Log("fetchRemoteEndpntPointer:create folder failed:" + ex.Message);
                }
            }

            //string localConfigFile = ConfigurationManager.GetCurrentDirectory() + @"\application.config";
            string localEndpointsFileTemp = endpointsDirectory + "endpoints.config.temp";
            string localEndpointsFile = endpointsDirectory + "endpoints.config";
            XmlDocument xmlDoc = new XmlDocument();

            try
            {
                ApplicationConfig appconfig = SsrsRenderStudio.Configuration.ConfigurationManager.ApplicationConfigFromConfigPointer;
                RenderStudioObjectConfig10 appInfo = null;

                if (appconfig.IsServer)
                    appInfo = SsrsRenderStudio.Configuration.ConfigurationManager.ServerInfoFromFriendlyName10(appconfig.ApplicationName);
                else if (appconfig.IsClient)
                    appInfo = SsrsRenderStudio.Configuration.ConfigurationManager.ClientInfoFromFriendlyName10(appconfig.ApplicationName);
                
                EndpointConfig endpointConfig = SsrsRenderStudio.Configuration.ConfigurationManager.EndpointConfigForApplication(appInfo.EndpointPointer);
                string remoteEndpointsFile = endpointConfig.URL;

                /*
                // DEPRECATED: clients are now hitting the master remote config as well
                // retrieve the remote location of endpoints file from configuration
                xmlDoc.Load(localConfigFile);
                XmlNode node = xmlDoc.SelectSingleNode("/configuration/endpoint");

                if (node == null) throw new Exception("Application configuration is invalid.");

                string remoteEndpointsFile = node.Attributes["location"].Value;
                */

                // download remote endpoints file
                System.Net.WebClient client = new System.Net.WebClient();

                try
                {
                    client.DownloadFile(remoteEndpointsFile, localEndpointsFileTemp);
                    // when download fails it deletes the destination file, so we need to temp it
                    System.IO.File.Copy(localEndpointsFileTemp, localEndpointsFile, true);
                    System.IO.File.Delete(localEndpointsFileTemp);
                    Logger.Log(EnumLoggingSeverity.INFORMATIONAL, "WcfConfiguration: " + "Fetching " + remoteEndpointsFile);
                }
                catch (Exception ex)
                {
                    Logger.Log(EnumLoggingSeverity.ALERT, "WcfConfiguration: " + "Fetching " + remoteEndpointsFile + " failed, will attempt to use a cached copy.");
                }

                if (!System.IO.File.Exists(localEndpointsFile))
                    throw new Exception("Local endpoints configuration pointer not available.");

            }
            catch (System.IO.FileNotFoundException ex)
            {
                Logger.Log(EnumLoggingSeverity.ALERT, "WcfConfiguration: " + ex.Message);
                return string.Empty;
            }
            catch (Exception ex)
            {
                Logger.Log(EnumLoggingSeverity.ALERT, "WcfConfiguration: " + "Fetching remote endpoint file failed : " + ex.Message);
                return string.Empty;
            }

            return localEndpointsFile;
        }

        /// <summary>
        /// Fetch the next binding configuration of a given service type.
        /// </summary>
        /// <param name="serviceType"></param>
        /// <returns>Absolute path to the fetched binding config file.</returns>
        public static void FetchNextBindingConfiguration(Type serviceType, out string bindingConfigurationFile )
        {
            bindingConfigurationFile = string.Empty;
            bool forceRebuild =false;

            if (string.IsNullOrEmpty(_localEndpointsFile))
            {
                forceRebuild = true;
                _localEndpointsFile = FetchRemoteEndpointsPointer();
                if (string.IsNullOrEmpty(_localEndpointsFile))
                    throw new Exception("The endpoints pointer configuration is not available.");
            }

            // rebuild bindings list from fetched config file
            if (_listOfBindings.Count == 0 || forceRebuild)
            {
                _listOfBindings = new Dictionary<string,List<string>>();
                _currentBindingPosition = new Dictionary<string,int>();
                XmlDocument xmlDoc = new XmlDocument();

                try
                {
                    xmlDoc.Load(_localEndpointsFile);
                    XmlNodeList nodes =  xmlDoc.SelectNodes("/configuration/endpoints");
                    
                    if (nodes.Count != 1)
                        throw new Exception("Endpoint binding configuration is invalid.");

                    foreach (XmlNode node in nodes[0])
                    {
                        string name = node.Attributes["name"].Value;
                        string location = node.Attributes["location"].Value;

                        if (_listOfBindings.Keys.Contains(name))
                            _listOfBindings[name].Add(location);
                        else
                        {
                            _listOfBindings.Add(name, new List<string>() { location });
                            _currentBindingPosition.Add(name, 0);
                        }
                    }
                }
                catch (Exception ex)
                {
                    throw ex;
                }
            }

            // retrieve the binding url information from the dictionary
            string svcType = serviceType.ToString();
            string remoteBindingConfigurationFile = string.Empty;
            try
            {
                remoteBindingConfigurationFile = _listOfBindings[svcType][_currentBindingPosition[svcType]];

                // if we're on the last index of the list then reset the binding position back to 0, else increment
                if (_currentBindingPosition[svcType] == _listOfBindings[svcType].Count() - 1)
                    _currentBindingPosition[svcType] = 0;
                else
                    _currentBindingPosition[svcType]++;
            }
            catch (Exception ex)
            {
                throw ex;
            }

            // fetch the remote binding config to local disk
            if (!string.IsNullOrEmpty(remoteBindingConfigurationFile))
            {
                string endpointsDirectory = SsrsRenderStudio.Configuration.ConfigurationManager.GetCurrentDirectory() + @"\endpoints\";
                int lastSlashIndex = remoteBindingConfigurationFile.LastIndexOf("/");
                if (lastSlashIndex == 0) lastSlashIndex = remoteBindingConfigurationFile.LastIndexOf(@"\");
                string localFileName = remoteBindingConfigurationFile.Substring(lastSlashIndex +1, remoteBindingConfigurationFile.Length - lastSlashIndex-1);
                string localFileNameTemp = localFileName + ".temp";

                System.Net.WebClient client = new System.Net.WebClient();

                try
                {
                    client.DownloadFile(remoteBindingConfigurationFile, endpointsDirectory + localFileNameTemp);
                    System.IO.File.Copy( endpointsDirectory + localFileNameTemp,  endpointsDirectory + localFileName, true);
                    System.IO.File.Delete(endpointsDirectory + localFileNameTemp);
                    Logger.Log(EnumLoggingSeverity.INFORMATIONAL, "WcfConfiguration: " + "Fetching " + remoteBindingConfigurationFile);
                }
                catch (Exception ex)
                {
                    Logger.Log(EnumLoggingSeverity.ALERT, "WcfConfiguration: " + "Fetching " + remoteBindingConfigurationFile + " failed, will attempt to use a cached copy.");
                }

                if (!System.IO.File.Exists(endpointsDirectory + localFileName))
                    throw new Exception("Local binding configuration not available.");

                bindingConfigurationFile = endpointsDirectory + localFileName;
            }

        }

        /// <summary>
        /// Calls close on IClientChannel, does not force abort.
        /// </summary>
        public static void CloseClientChannel<T>(T proxiedChannel)
        {
            try
            {
                ((IClientChannel)proxiedChannel).Close();
            }
            catch
            { }
        }

        /// <summary>
        /// Sends enpoint info to the logger.
        /// </summary>
        public static void PrintServiceInfo(ServiceHost host)
        {
            Logger.Log(EnumLoggingSeverity.INFORMATIONAL, "WcfConfiguration: " + host.Description.ServiceType.ToString());
            foreach (ServiceEndpoint e in host.Description.Endpoints)
                Logger.Log(EnumLoggingSeverity.INFORMATIONAL, "WcfConfiguration: " + "\t" + e.Address.ToString());
        }


        /*
        private static readonly string ENDPOINT_CONFIGURATION_POINTER_FILE = "endpoints.config";
        private static readonly string ENDPOINT_SECTION_NAME = "/configuration/endpoints";
        private static readonly string ENDPOINT_NAME_ATTRIBUTE = "name";
        private static readonly string ENDPOINT_LOCATION_ATTRIBUTE = "location";
        private static int ENDPOINT_COUNTER = 0;

        private static string _endpointsConfigPointerFile
        {
            get
            {
                return ConfigurationManager.GetCurrentDirectory() + @"\" + ENDPOINT_CONFIGURATION_POINTER_FILE;
            }
        }

        private static bool _endpointConfigurationFileCounterReset = false;

        /// <summary>
        /// Self-resetting boolean switch.  Resets on every Get.
        /// </summary>
        public static bool EndpointConfigurationFileCounterReset
        {
            get
            {
                bool temp = _endpointConfigurationFileCounterReset;
                _endpointConfigurationFileCounterReset = false;
                return temp;
            }
        }

        //TODO : this returns in round robin fashion.  might not be desirable for a server with multiple servicehosts.  
        //              each servicehost would get a different listenre
        //  this should be ok for clients though.
        public static string EndpointConfigurationFile
        {
            get
            {
                try
                {
                    if (!System.IO.File.Exists(_endpointsConfigPointerFile))
                    {
                        Logger.Log(_endpointsConfigPointerFile + " does not exist!");
                        return string.Empty;
                    }

                    XmlDocument xmlDoc = new XmlDocument();
                    string remotePath = string.Empty;

                    try
                    {
                        xmlDoc.Load(_endpointsConfigPointerFile);
                        XmlNode xmlNode = xmlDoc.SelectNodes(ENDPOINT_SECTION_NAME)[0];
                        remotePath = xmlNode.ChildNodes[ENDPOINT_COUNTER].Attributes[ENDPOINT_LOCATION_ATTRIBUTE].Value.Trim();

                        try
                        {
                            // test for next config location in line, if none found then we do not increment counter.
                            xmlNode.ChildNodes[ENDPOINT_COUNTER + 1].Attributes[ENDPOINT_LOCATION_ATTRIBUTE].Value.Trim();
                            ENDPOINT_COUNTER++;
                        }
                        catch 
                        {
                            // reset counter to first node.
                            ENDPOINT_COUNTER = 0;
                            _endpointConfigurationFileCounterReset = true;
                        }
                    }
                    catch (Exception ex)
                    {
                        return string.Empty;
                    }

                    string localFile = string.Empty;
                    if (! FetchRemoteFile(remotePath, out localFile))
                        return string.Empty;

                    return localFile;
                }
                catch
                {
                    return string.Empty;
                }               
            }
        }

        
        private static string _localEndpointsPointerPath
        {
            get
            {
                return ConfigurationManager.GetCurrentDirectory() + @"\endpoints\endpoints.config";
            }
        }

        

        private static bool FetchRemoteFile(string remoteFile, out string localFile)
        {
            bool result = true;

            string currentDir = ConfigurationManager.GetCurrentDirectory() + @"\";
            string tempFile = "fetch.temp";

            try
            {
                System.IO.File.Delete(currentDir  + tempFile);
            }
            catch(System.IO.FileNotFoundException ex) {}
            catch(Exception ex)
            {
                tempFile = Guid.NewGuid().ToString();
            }

            localFile = currentDir + tempFile;

            System.Net.WebClient client = new System.Net.WebClient();

            try
            {
                client.DownloadFile(remoteFile, localFile);
                Logger.Log("Fetch: " + remoteFile);
            }
            catch (System.Net.WebException ex)
            {
                result = false;
            }
            catch (Exception ex)
            {
                result = false;
            }

            return result;
        }
        */
    }
}
